﻿using DotNetCore.CAP;
using Google.Protobuf.WellKnownTypes;
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using Microsoft.Extensions.Logging;
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ZhonTai.Admin.Core;
using ZhonTai.Industry.Contracts.Communication;
using ZhonTai.Industry.Contracts.Core.Enums;
using ZhonTai.Industry.Contracts.Model.Dic;
using ZhonTai.Industry.Contracts.Model.Entity;
using DB = ZhonTai.Industry.Contracts.Model.Entity.DB;
using OperateResult = HslCommunication.OperateResult;

namespace ZhonTai.Industry.Contracts.Interactive
{
    /// <summary>
    /// 工艺采集监控
    /// </summary>
    [Serializable]
    public sealed class ProcessMonitor : BaseMonitor
    {
        public DataShip _ship;
        public DB _plcDB;
        public DB _mesDB;

        #region 私有字段

        private WatchDogMonitor _watchDogMonitor;
        private SiemensPlc _plc;

        //PLC数据块地址
        private string _plcAddress;

        //PLC数据块长度
        private ushort _length;

        private readonly ICapPublisher _cap;
        private readonly int _partitions;
        private readonly ILogger<ProcessMonitor> _logger;
        private readonly Dictionary<string, object>[] _dataStore;
        private readonly ReaderWriterLockSlim[] _partitionLocks;
        private readonly ConcurrentDictionary<string, FieldMetadata> _fieldMetadataCache;
        private readonly ArrayPool<PushShip> _pushParamPool;

        #endregion 私有字段

        public ProcessMonitor(ILogger<ProcessMonitor> logger, int partitions = 64)
        {
            _partitions = partitions;
            _logger = logger;
            _cap = AppInfo.GetRequiredService<ICapPublisher>();
            _dataStore = new Dictionary<string, object>[_partitions];
            _partitionLocks = new ReaderWriterLockSlim[_partitions];
            _pushParamPool = ArrayPool<PushShip>.Shared;
            _fieldMetadataCache = new ConcurrentDictionary<string, FieldMetadata>();
        }

        /// <summary>
        /// 参数初始化
        /// </summary>
        /// <param name="dataShip"></param>
        public void Initialization(DataShip dataShip)
        {
            _plc = InstanceDic.PLC[dataShip.StationId];
            _plcDB = dataShip.PLCDB;
            _mesDB = dataShip.MESDB;
            _plcAddress = "DB" + _plcDB.DBAddress + ".0";
            _length = _plcDB.DBLength;
            CacheFieldMetadata(dataShip.PLCDB);
            CacheLastValue(dataShip.PLCDB);
            HeartWrite(dataShip);
            //VoltaAmpere();
            _ship = dataShip;
        }

        public override void Stop()
        {
            _watchDogMonitor?.Stop();
            base.Stop();
        }

        /// <summary>
        /// 抽离关键点位
        /// </summary>
        /// <param name="db"></param>
        private void CacheFieldMetadata(DB db)
        {
            foreach (var field in db.DataFields.Concat(db.KeyFields))
            {
                _fieldMetadataCache[field.Another] = new FieldMetadata(
                    field.Offset,
                    field.Topic,
                    field.GroupName,
                    field.PLCDataType,
                    isKeyField: db.KeyFields.Contains(field)
                );
            }
        }

        /// <summary>
        /// 缓存最新值以及定义锁
        /// </summary>
        /// <param name="db"></param>
        private void CacheLastValue(DB db)
        {
            for (int i = 0; i < _partitions; i++)
            {
                _dataStore[i] = new Dictionary<string, object>();
                _partitionLocks[i] = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
            }
        }

        /// <summary>
        /// 心跳
        /// </summary>
        /// <param name="dataShip"></param>
        private void HeartWrite(DataShip dataShip)
        {
            Task.Run(() =>
            {
                var heartField = dataShip.MESDB.DataFields
                    .FirstOrDefault(f => f.Another == "HeartToPlc");
                var _heartOffset = heartField?.Offset ?? 0;
                _watchDogMonitor = new WatchDogMonitor(1000);
                _watchDogMonitor.Initialization(_plc, $"DB{_mesDB.DBAddress}.{heartField.Offset}", 2);
                _watchDogMonitor?.Start();
            });
        }

        protected override void Execute()
        {
            try
            {
                var result = _plc.Read(_plcAddress, _length);
                if (result.IsSuccess)
                {
                    ByteSlice(result.Content);
                }
                else
                {
                    _logger.LogError($"工站【{_ship.OPCode}】读取数据内部报错原因：{result.Message}！");
                }
            }
            catch
            {
            }
        }
        /// <summary>
        /// 数组切片解析
        /// </summary>
        private void ByteSlice(byte[] input)
        {
            Parallel.ForEach(_fieldMetadataCache.Where(kv => !kv.Value.IsKeyField), item =>
            {
                var currentValue = ParseValue(input, item.Value.Offset, item.Value.DataType);
                UpdateDataStore(item.Key, currentValue, triggerNotification: false);
            });
            foreach (var item in _fieldMetadataCache.Where(kv => kv.Value.IsKeyField))
            {
                var currentValue = ParseValue(input, item.Value.Offset, item.Value.DataType);
                UpdateDataStore(item.Key, currentValue, triggerNotification: true);
            }
        }

        /// <summary>
        /// byte数组转换
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="dataType"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private object ParseValue(byte[] buffer, double offset, PLCDataTypeEnum dataType)
        {
            try
            {
                int intOffset = (int)offset;
                return dataType switch
                {
                    PLCDataTypeEnum.BOOL => ParseBool(buffer, offset),
                    PLCDataTypeEnum.BYTE => buffer[(int)offset],
                    PLCDataTypeEnum.WORD => BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(intOffset, 2)),
                    PLCDataTypeEnum.DWORD => BinaryPrimitives.ReadUInt32BigEndian(buffer.AsSpan(intOffset, 4)),
                    PLCDataTypeEnum.INT => BinaryPrimitives.ReadInt16BigEndian(buffer.AsSpan(intOffset, 2)),
                    PLCDataTypeEnum.DINT => BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(intOffset, 4)),
                    PLCDataTypeEnum.REAL => BinaryPrimitives.ReadSingleBigEndian(buffer.AsSpan(intOffset, 4)),
                    PLCDataTypeEnum.LREAL => BinaryPrimitives.ReadDoubleBigEndian(buffer.AsSpan(intOffset, 8)),
                    PLCDataTypeEnum.STRING => ParsePlcString(buffer, intOffset),
                    _ => -101
                };
            }
            catch (Exception ex)
            {
                _logger.LogError($"字段解析错误，工站【{_ship.OPCode}】批偏移量【{offset}】,原因：【{ex.Message}】");
                return -500;
            }
        }

        /// <summary>
        /// 更新解析值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="triggerNotification"></param>
        private void UpdateDataStore(string key, object value, bool triggerNotification)
        {
            int partition = Math.Abs(HashCode.Combine(key)) % _partitions;
            var lockObj = _partitionLocks[partition];

            try
            {
                lockObj.EnterUpgradeableReadLock();

                if (_dataStore[partition].TryGetValue(key, out var oldVal) &&
                    Equals(oldVal, value))
                {
                    return;
                }
                try
                {
                    lockObj.EnterWriteLock();
                    _dataStore[partition][key] = value;
                }
                finally
                {
                    lockObj.ExitWriteLock();
                }
                if (triggerNotification)
                {
                    TriggerNotification(key, value);
                }
            }
            finally
            {
                lockObj.ExitUpgradeableReadLock();
            }
        }

        /// <summary>
        /// 推送订阅
        /// </summary>
        /// <param name="key"></param>
        /// <param name="newVal"></param>
        private void TriggerNotification(string key, object newVal)
        {
            var paramArray = _pushParamPool.Rent(1);
            try
            {
                var param = paramArray[0] ?? new PushShip();
                if (_fieldMetadataCache.TryGetValue(key, out var metadata))
                {
                    param.StationId = _ship.StationId;
                    param.OPCode = _ship.OPCode;
                    param.GroupName = metadata.GroupName;
                    param.Another = key;
                    param.Value = newVal;
                    _ = _cap.PublishAsync("ProcessMonitor", param);
                }
            }
            finally
            {
                _pushParamPool.Return(paramArray);
            }
        }

        /// <summary>
        /// 获取最新采集值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T TryGetValue<T>(string key)
        {
            int partition = Math.Abs(HashCode.Combine(key)) % _partitions;
            var lockObj = _partitionLocks[partition];
            try
            {
                lockObj.EnterReadLock();

                if (_dataStore[partition].TryGetValue(key, out var objVal))
                {
                    try
                    {
                        return (T)Convert.ChangeType(objVal, typeof(T));
                    }
                    catch
                    {
                        return default;
                    }
                }
                return default;
            }
            finally
            {
                lockObj.ExitReadLock();
            }
        }

        /// <summary>
        /// 通用写入
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="offset"></param>
        /// <param name="value"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public async Task<OperateResult> WriteAsync<T>(double offset, T value, int? db = null)
        {
            try
            {
                var dataType = System.Type.GetTypeCode(typeof(T)) switch
                {
                    TypeCode.Boolean => PLCDataTypeEnum.BOOL,
                    TypeCode.Byte => PLCDataTypeEnum.BYTE,
                    TypeCode.UInt16 => PLCDataTypeEnum.WORD,
                    TypeCode.UInt32 => PLCDataTypeEnum.DWORD,
                    TypeCode.Int16 => PLCDataTypeEnum.INT,
                    TypeCode.Int32 => PLCDataTypeEnum.DINT,
                    TypeCode.Single => PLCDataTypeEnum.REAL,
                    TypeCode.Double => PLCDataTypeEnum.LREAL,
                    TypeCode.String => PLCDataTypeEnum.STRING,
                    _ => throw new NotSupportedException($"不支持的类型: {typeof(T).Name}")
                };
                var address = $"DB{db ?? _mesDB.DBAddress}.{offset}";
                OperateResult result = await WriteCoreAsync(value, dataType, address);
                while (true)
                {
                    var res = dataType switch
                    {
                        PLCDataTypeEnum.INT => await _plc.ReadInt16Async(address)
                    };
                    if (res.IsSuccess && Equals(res.Content, value))
                    {
                        break;
                    }
                    else
                    {
                        await WriteCoreAsync(value, dataType, address);
                    }
                }
                return new OperateResult() { IsSuccess = true };
            }
            catch (Exception ex)
            {
                return new OperateResult { IsSuccess = false, Message = ex.Message };
            }
        }

        /// <summary>
        /// 写入核心
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="dataType"></param>
        /// <param name="address"></param>
        /// <returns></returns>
        private async Task<OperateResult> WriteCoreAsync<T>(T value, PLCDataTypeEnum dataType, string address)
        {
            return dataType switch
            {
                PLCDataTypeEnum.BOOL when value is bool boolVal =>
                    await _plc.WriteAsync(address, boolVal),
                PLCDataTypeEnum.BYTE when value is byte byteVal =>
                    await _plc.WriteAsync(address, byteVal),
                PLCDataTypeEnum.WORD when value is ushort ushortVal =>
                    await _plc.WriteAsync(address, ushortVal),
                PLCDataTypeEnum.DWORD when value is uint uintVal =>
                    await _plc.WriteAsync(address, uintVal),
                PLCDataTypeEnum.INT when value is short shortVal =>
                    await _plc.WriteAsync(address, shortVal),
                PLCDataTypeEnum.DINT when value is int intVal =>
                    await _plc.WriteAsync(address, intVal),
                PLCDataTypeEnum.REAL when value is float floatVal =>
                    await _plc.WriteAsync(address, floatVal),
                PLCDataTypeEnum.LREAL when value is double doubleVal =>
                    await _plc.WriteAsync(address, doubleVal),
                PLCDataTypeEnum.STRING when value is string strVal =>
                    await _plc.WriteAsync(address, strVal),
                _ => new OperateResult
                {
                    IsSuccess = false,
                    Message = "类型不匹配或不受支持"
                }
            };
        }

        #region 特殊类型解析

        /// <summary>
        /// BOOL类型转换
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool ParseBool(byte[] buffer, double offset)
        {
            // 参数验证
            if (offset < 0)
                throw new ArgumentOutOfRangeException(nameof(offset), "偏移量不能为负数");
            // 分离整数部分和小数部分
            int byteIndex = (int)Math.Truncate(offset);
            double fractionalPart = offset - byteIndex;
            // 计算位索引
            int bitIndex = (int)(Math.Round(fractionalPart, 1) * 10);
            // 验证位索引范围
            if (bitIndex < 0 || bitIndex > 7)
                throw new ArgumentOutOfRangeException(nameof(offset), "位索引必须在 0-7 之间");
            return byteIndex < buffer.Length && (buffer[byteIndex] & (1 << bitIndex)) != 0;
        }

        /// <summary>
        /// STRING类型转换
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private string ParsePlcString(byte[] buffer, int offset)
        {
            if (offset + 2 >= buffer.Length) return string.Empty;

            try
            {
                int maxLen = buffer[offset];
                int currentLen = Math.Min(buffer[offset + 1], maxLen);
                int start = offset + 2;

                if (start + currentLen > buffer.Length)
                    currentLen = Math.Max(buffer.Length - start, 0);

                return Encoding.UTF8.GetString(buffer, start, currentLen);
            }
            catch
            {
                return string.Empty;
            }
        }

        #endregion 特殊类型解析
    }
}